package edu.uwm.universitydb.encryption;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

/**
 * Field encryption: The encryption algorithm can be 3DES or AES. The encryption
 * key should be stored in a USB drive. A discussion about how to perform query
 * in such encrypted database should be included.
 */

public class JavaEncryption {

    private static final String ALGORITHM = "AES";
    private static final String ENCODING = "UTF8";
    private static final String KEYFILE = "universitydb.key";
    private final SecretKeySpec _keySpec;
    private static JavaEncryption _jce;

    public JavaEncryption() throws MissingKeyException {
        _keySpec = new SecretKeySpec(readKeyFile().getBytes(), ALGORITHM);
    }

    public synchronized static JavaEncryption getInstance() throws MissingKeyException {
        if (_jce == null) {
            _jce = new JavaEncryption();
        }
        return _jce;
    }

    /**
     * Encrypt the given Integer
     * @return Encrypted string in BASE64 format
     */
    public String encrypt(Integer num) {
        return encrypt(num.toString());
    }

    /**
     * Encrypt the given string
     * @return Encrypted string in BASE64 format
     */
    public String encrypt(String text) {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, _keySpec);
            byte[] cipherEncrypter = cipher.doFinal(text.getBytes(ENCODING));
            return DatatypeConverter.printBase64Binary(cipherEncrypter);
        }
        catch (InvalidKeyException | BadPaddingException
                | IllegalBlockSizeException | NoSuchAlgorithmException
                | NoSuchPaddingException | UnsupportedEncodingException e) {
            throw new SecurityCryptoException(e);
        }
    }

    /**
     * Decrypt the given BASE64 formatted string
     */
    public String decrypt(String encryptedMsg) {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, _keySpec);
            byte[] base64decrypter = DatatypeConverter.parseBase64Binary(encryptedMsg);
            return new String(cipher.doFinal(base64decrypter));
        }
        catch (InvalidKeyException | BadPaddingException
                | IllegalBlockSizeException | NoSuchAlgorithmException
                | NoSuchPaddingException e) {
            throw new SecurityCryptoException(e);
        }
    }

    /**
     * This function checks for <code>KEYFILE</code> in all root directories
     * @throws MissingKeyException If file not found or cannot be read
     */
    private String readKeyFile() throws MissingKeyException {
        String key = null;
        try {
            File[] drives = File.listRoots();
            if (drives != null) {
                for (File drive : drives) {
                    File keyFile = new File(drive, KEYFILE);
                    if (keyFile.exists()) {
                        key = new String(Files.readAllBytes(keyFile.toPath()));
                    }
                }
            }
            else {
                throw new MissingKeyException("Failed not locate USB drive.");
            }
        }
        catch (IOException | MissingKeyException e) {
            throw new MissingKeyException(e);
        }
        if (key == null || key.isEmpty()) {
            throw new MissingKeyException("Failed to read key file.");
        }
        return key;
    }

}
